package com.test.drawing;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

import android.graphics.Bitmap;
import android.opengl.GLUtils;

/**
 * Mesh from a OpenGL 3D Model which draws its Object in a OpenGL Surface.
 */
public abstract class Mesh implements Drawable {

	private static final int FLOAT_BYTE_SIZE = 4;

	// Our texture id.
	private int mTextureId = -1;

	// The bitmap we want to load as a texture.
	private Bitmap mBitmap;

	// Indicates if we need to load the texture.
	private boolean mShouldLoadTexture = false;

	// MeshBuffer
	private MeshBuffer meshBuffer = null;

	// Default Flat Color
	private final float[] mRGBA = new float[] { 1.0f, 1.0f, 1.0f, 1.0f };

	// Smooth Colors
	private FloatBuffer mColorBuffer = null;

	// Translate params.
	private float x = 0;
	private float y = 0;
	private float z = 0;

	// Rotate params.
	private float rx = 0;
	private float ry = 0;
	private float rz = 0;
	
	public Mesh(MeshBuffer meshBuffer) {
		setMeshBuffer(meshBuffer);
	}

	/*
	 * Enable and points the Normal Buffer
	 */
	protected void enableNormals(GL10 gl) {
		// Define Normals
		if (meshBuffer.getNormalBuffer() != null) {

			gl.glEnableClientState(GL10.GL_NORMAL_ARRAY);
			gl.glNormalPointer(GL10.GL_FLOAT, 0, meshBuffer.getNormalBuffer());
		}
	}
	
	public MeshBuffer getMeshBuffer() {
		return meshBuffer;
	}
	
	public void setMeshBuffer(MeshBuffer meshBuffer) {
		this.meshBuffer = meshBuffer;
	}

	/*
	 * Enable, Loads (if needed) and Points the Texture Buffer
	 */

	protected void enableTextures(GL10 gl) {

		if (mShouldLoadTexture) {
			loadGLTexture(gl);
			mShouldLoadTexture = false;
		}

		if (mTextureId != -1 && meshBuffer.getTextureBuffer() != null) {
			gl.glEnable(GL10.GL_TEXTURE_2D);
			// Enable the texture state
			gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);

			// Point to our buffers
			gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, meshBuffer.getTextureBuffer());
			gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);
		}
	}

	/*
	 * Disable Texture Coordinate Array
	 */
	protected void disableTextures(GL10 gl) {
		gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
	}

	protected void enableColors(GL10 gl) {

		// Set flat color with lightning
		gl.glMaterialfv(GL10.GL_FRONT_AND_BACK, GL10.GL_DIFFUSE, mRGBA, 0);

		// Smooth color
		// if (mColorBuffer != null) {
		// // Enable the color array buffer to be used during rendering.
		// gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
		// gl.glColorPointer(4, GL10.GL_FLOAT, 0, mColorBuffer);
		// }
	}

	protected void enableVertex(GL10 gl) {
		// Enabled the vertices buffer for writing and to be used during
		// rendering.
		gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
		// Specifies the location and data format of an array of vertex
		// coordinates to use when rendering.
		gl.glVertexPointer(3, GL10.GL_FLOAT, 0, meshBuffer.getVerticesBuffer());
	}

	/**
	 * Disable Vertex Array
	 * 
	 * @param gl
	 */
	protected void disableVertex(GL10 gl) {
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
	}

	protected void enableFeatures(GL10 gl) {

		// Counter-clockwise winding.
		gl.glFrontFace(GL10.GL_CCW);

		// Enable face culling.
		gl.glEnable(GL10.GL_CULL_FACE);

		// What faces to remove with the face culling.
		gl.glCullFace(GL10.GL_BACK);

		enableVertex(gl);

		if (meshBuffer.getNormalBuffer() != null)
			enableNormals(gl);

		enableColors(gl);

		// Arrumar Texturas
		enableTextures(gl);

	}

	/**
	 * Render the mesh.
	 * 
	 * @param gl
	 *            the OpenGL context to render to.
	 */
	public void draw(GL10 gl) {

		enableFeatures(gl);

		gl.glPushMatrix();

		gl.glTranslatef(x, y, z);
		gl.glRotatef(rx, 1, 0, 0);
		gl.glRotatef(ry, 0, 1, 0);
		gl.glRotatef(rz, 0, 0, 1);

		drawElements(gl);

		gl.glPopMatrix();

		disableFeatures(gl);
	}

	protected void drawElements(GL10 gl) {
		// Point out the where the color buffer is.
		gl.glDrawElements(GL10.GL_TRIANGLES, meshBuffer.getIndiceSize(),
				GL10.GL_UNSIGNED_SHORT, meshBuffer.getIndicesBuffer());
	}

	protected void disableFeatures(GL10 gl) {
		gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);

		disableTextures(gl);

		// Disable face culling.
		gl.glDisable(GL10.GL_CULL_FACE);
	}


	/**
	 * Set one flat color on the mesh.
	 * 
	 * @param red
	 * @param green
	 * @param blue
	 * @param alpha
	 */
	protected void setColor(float red, float green, float blue, float alpha) {
		mRGBA[0] = red;
		mRGBA[1] = green;
		mRGBA[2] = blue;
		mRGBA[3] = alpha;
	}

	/**
	 * Set the colors
	 * 
	 * @param colors
	 */
	protected void setColors(float[] colors) {
		// float has 4 bytes.
		ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length
				* FLOAT_BYTE_SIZE);
		cbb.order(ByteOrder.nativeOrder());
		mColorBuffer = cbb.asFloatBuffer();
		mColorBuffer.put(colors);
		mColorBuffer.position(0);
	}

	/**
	 * Set the bitmap to load into a texture.
	 * 
	 * @param bitmap
	 */
	public void loadBitmap(Bitmap bitmap) { // New function.
		if (bitmap == null)
			return;
		this.mBitmap = bitmap;
		mShouldLoadTexture = true;
	}

	/**
	 * Loads the texture.
	 * 
	 * @param gl
	 */
	private void loadGLTexture(GL10 gl) { // New function
		// Generate one texture pointer...
		int[] textures = new int[1];
		gl.glGenTextures(1, textures, 0);
		mTextureId = textures[0];

		// ...and bind it to our array
		gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureId);

		// Create Nearest Filtered Texture
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER,
				GL10.GL_LINEAR);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER,
				GL10.GL_LINEAR);

		// Different possible texture parameters, e.g. GL10.GL_CLAMP_TO_EDGE
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
				GL10.GL_CLAMP_TO_EDGE);
		gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
				GL10.GL_CLAMP_TO_EDGE); // GL_REPEAT

		// Use the Android GLUtils to specify a two-dimensional texture image
		// from our bitmap
		GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, mBitmap, 0);
	}

	/**
	 * Gets the Mesh's Position in X Axis
	 * 
	 * @return float - x position
	 */
	public float getX() {
		return x;
	}

	/**
	 * Sets the current Mesh's Position in X Axis
	 * 
	 * @param x
	 *            float - new x axis position
	 */
	public void setX(float x) {
		this.x = x;
	}

	/**
	 * Gets the Mesh's Position in Y Axis
	 * 
	 * @return float - y position
	 */
	public float getY() {
		return y;
	}

	/**
	 * Sets the current Mesh's Position in Y Axis
	 * 
	 * @param y
	 *            float - new y axis position
	 */
	public void setY(float y) {
		this.y = y;
	}

	/**
	 * Gets the current Mesh's Rotation in angles on X Axis
	 * 
	 * @return float - Angles Rotation in X Axis
	 */
	public float getRotationAngleX() {
		return rx;
	}

	/**
	 * Gets the current Mesh's Rotation in angles on Y Axis
	 * 
	 * @return float - Angles Rotation in Y Axis
	 */
	public float getRotationAngleY() {
		return ry;
	}

	/**
	 * Gets the current Mesh's Rotation in angles on Z Axis
	 * 
	 * @return float - Angles Rotation in Z Axis
	 */
	public float getRotationAngleZ() {
		return rz;
	}

	/**
	 * Sets the current Mesh's Rotation in angles on X Axis
	 * 
	 * @param rx
	 *            float - New Angles Rotation
	 */
	public void setRotationAngleX(float rx) {
		this.rx = rx;
	}

	/**
	 * Sets the current Mesh's Rotation in angles on Y Axis
	 * 
	 * @param ry
	 *            float - New Angles Rotation
	 */
	public void setRotationAngleY(float ry) {
		this.ry = ry;
	}

	/**
	 * Sets the current Mesh's Rotation in angles on Z Axis
	 * 
	 * @param rz
	 *            float - New Angles Rotation
	 */
	public void setRotationAngleZ(float rz) {
		this.rz = rz;
	}

	/**
	 * Gets the Mesh's Position in Z Axis
	 * 
	 * @return float - z position
	 */
	public float getZ() {
		return z;
	}

	/**
	 * Sets the current Mesh's Position in Z Axis
	 * 
	 * @param z
	 *            float - new z axis position
	 */
	public void setZ(float z) {
		this.z = z;
	}

	/**
	 * Sets the Current Mesh Position
	 * 
	 * @param x
	 *            position in X Axis
	 * @param y
	 *            position in Y Axis
	 * @param z
	 *            position in Z Axis
	 */
	public void setPosition(float x, float y, float z) {
		this.x = x;
		this.y = y;
		this.z = z;
	}

	/**
	 * Gets an Array containing the Current Position of this Mesh
	 * 
	 * @return Array containing X, Y and Z Coodinates of this Mesh
	 */
	public float[] getPosition() {
		return new float[] { x, y, z };
	}
}
